/* -*-mode:java; c-basic-offset:2; indent-tabs-mode:nil -*- */
/* -*-mode:java; c-basic-offset:2; indent-tabs-mode:nil -*- */
/* JOrbis
 * Copyright (C) 2000 ymnk, JCraft,Inc.
 *  
 * Written by: 2000 ymnk<ymnk@jcraft.com>
 *   
 * Many thanks to 
 *   Monty <monty@xiph.org> and 
 *   The XIPHOPHORUS Company http://www.xiph.org/ .
 * JOrbis has been based on their awesome works, Vorbis codec.
 *   
 * This program is free software; you can redistribute it and/or
 * modify it under the terms of the GNU Library General Public License
 * as published by the Free Software Foundation; either version 2 of
 * the License, or (at your option) any later version.

 * This program is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 * GNU Library General Public License for more details.
 * 
 * You should have received a copy of the GNU Library General Public
 * License along with this program; if not, write to the Free Software
 * Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
 */

package com.jcraft.jorbis;

import com.jcraft.jogg.*;

import java.io.InputStream;
import java.io.IOException;

public class VorbisFile {
	static final int CHUNKSIZE = 8500;
	static final int SEEK_SET = 0;
	static final int SEEK_CUR = 1;
	static final int SEEK_END = 2;

	static final int OV_FALSE = -1;
	static final int OV_EOF = -2;
	static final int OV_HOLE = -3;

	static final int OV_EREAD = -128;
	static final int OV_EFAULT = -129;
	static final int OV_EIMPL = -130;
	static final int OV_EINVAL = -131;
	static final int OV_ENOTVORBIS = -132;
	static final int OV_EBADHEADER = -133;
	static final int OV_EVERSION = -134;
	static final int OV_ENOTAUDIO = -135;
	static final int OV_EBADPACKET = -136;
	static final int OV_EBADLINK = -137;
	static final int OV_ENOSEEK = -138;

	InputStream datasource;
	boolean seekable = false;
	long offset;
	long end;

	SyncState oy = new SyncState();

	int links;
	long[] offsets;
	long[] dataoffsets;
	int[] serialnos;
	long[] pcmlengths;
	Info[] vi;
	Comment[] vc;

	// Decoding working state local storage
	long pcm_offset;
	boolean decode_ready = false;

	int current_serialno;
	int current_link;

	float bittrack;
	float samptrack;

	StreamState os = new StreamState(); // take physical pages, weld into a
										// logical
	// stream of packets
	DspState vd = new DspState(); // central working state for
	// the packet->PCM decoder
	Block vb = new Block(vd); // local working space for packet->PCM decode

	// ov_callbacks callbacks;

	public VorbisFile(String file) throws JOrbisException {
		super();
		InputStream is = null;
		try {
			is = new SeekableInputStream(file);
			int ret = open(is, null, 0);
			if (ret == -1) {
				throw new JOrbisException("VorbisFile: open return -1");
			}
		} catch (Exception e) {
			throw new JOrbisException("VorbisFile: " + e.toString());
		} finally {
			if (is != null) {
				try {
					is.close();
				} catch (IOException e) {
					e.printStackTrace();
				}
			}
		}
	}

	public VorbisFile(InputStream is, byte[] initial, int ibytes)
			throws JOrbisException {
		super();
		int ret = open(is, initial, ibytes);
		if (ret == -1) {
		}
	}

	private int get_data() {
		int index = oy.buffer(CHUNKSIZE);
		byte[] buffer = oy.data;
		int bytes = 0;
		try {
			bytes = datasource.read(buffer, index, CHUNKSIZE);
		} catch (Exception e) {
			return OV_EREAD;
		}
		oy.wrote(bytes);
		if (bytes == -1) {
			bytes = 0;
		}
		return bytes;
	}

	private void seek_helper(long offst) {
		fseek(datasource, offst, SEEK_SET);
		this.offset = offst;
		oy.reset();
	}

	private int get_next_page(Page page, long boundary) {
		if (boundary > 0)
			boundary += offset;
		while (true) {
			int more;
			if (boundary > 0 && offset >= boundary)
				return OV_FALSE;
			more = oy.pageseek(page);
			if (more < 0) {
				offset -= more;
			} else {
				if (more == 0) {
					if (boundary == 0)
						return OV_FALSE;
					int ret = get_data();
					if (ret == 0)
						return OV_EOF;
					if (ret < 0)
						return OV_EREAD;
				} else {
					int ret = (int) offset; // !!!
					offset += more;
					return ret;
				}
			}
		}
	}

	private int get_prev_page(Page page) throws JOrbisException {
		long begin = offset; // !!!
		int ret;
		int offst = -1;
		while (offst == -1) {
			begin -= CHUNKSIZE;
			if (begin < 0)
				begin = 0;
			seek_helper(begin);
			while (offset < begin + CHUNKSIZE) {
				ret = get_next_page(page, begin + CHUNKSIZE - offset);
				if (ret == OV_EREAD) {
					return OV_EREAD;
				}
				if (ret < 0) {
					if (offst == -1)
						throw new JOrbisException();
					break;
				} else {
					offst = ret;
				}
			}
		}
		seek_helper(offst); // !!!
		ret = get_next_page(page, CHUNKSIZE);
		if (ret < 0) {
			return OV_EFAULT;
		}
		return offst;
	}

	int bisect_forward_serialno(long begin, long searched, long end,
			int currentno, int m) {
		long endsearched = end;
		long next = end;
		Page page = new Page();
		int ret;

		while (searched < endsearched) {
			long bisect;
			if (endsearched - searched < CHUNKSIZE) {
				bisect = searched;
			} else {
				bisect = (searched + endsearched) / 2;
			}

			seek_helper(bisect);
			ret = get_next_page(page, -1);
			if (ret == OV_EREAD)
				return OV_EREAD;
			if (ret < 0 || page.serialno() != currentno) {
				endsearched = bisect;
				if (ret >= 0)
					next = ret;
			} else {
				searched = ret + page.header_len + page.body_len;
			}
		}
		seek_helper(next);
		ret = get_next_page(page, -1);
		if (ret == OV_EREAD)
			return OV_EREAD;

		if (searched >= end || ret == -1) {
			links = m + 1;
			offsets = new long[m + 2];
			offsets[m + 1] = searched;
		} else {
			ret = bisect_forward_serialno(next, offset, end, page.serialno(),
					m + 1);
			if (ret == OV_EREAD)
				return OV_EREAD;
		}
		offsets[m] = begin;
		return 0;
	}

	// uses the local ogg_stream storage in vf; this is important for
	// non-streaming input sources
	int fetch_headers(Info vi, Comment vc, int[] serialno, Page og_ptr) {
		Page og = new Page();
		Packet op = new Packet();
		int ret;

		if (og_ptr == null) {
			ret = get_next_page(og, CHUNKSIZE);
			if (ret == OV_EREAD)
				return OV_EREAD;
			if (ret < 0)
				return OV_ENOTVORBIS;
			og_ptr = og;
		}

		if (serialno != null)
			serialno[0] = og_ptr.serialno();

		os.init(og_ptr.serialno());

		// extract the initial header from the first page and verify that the
		// Ogg bitstream is in fact Vorbis data

		vi.init();
		vc.init();

		int i = 0;
		while (i < 3) {
			os.pagein(og_ptr);
			while (i < 3) {
				int result = os.packetout(op);
				if (result == 0)
					break;
				if (result == -1) {
					vi.clear();
					vc.clear();
					os.clear();
					return -1;
				}
				if (vi.synthesis_headerin(vc, op) != 0) {
					vi.clear();
					vc.clear();
					os.clear();
					return -1;
				}
				i++;
			}
			if (i < 3)
				if (get_next_page(og_ptr, 1) < 0) {
					vi.clear();
					vc.clear();
					os.clear();
					return -1;
				}
		}
		return 0;
	}

	// last step of the OggVorbis_File initialization; get all the
	// vorbis_info structs and PCM positions. Only called by the seekable
	// initialization (local stream storage is hacked slightly; pay
	// attention to how that's done)
	void prefetch_all_headers(Info first_i, Comment first_c, int dataoffset)
			throws JOrbisException {
		Page og = new Page();
		int ret;

		vi = new Info[links];
		vc = new Comment[links];
		dataoffsets = new long[links];
		pcmlengths = new long[links];
		serialnos = new int[links];

		for (int i = 0; i < links; i++) {
			if (first_i != null && first_c != null && i == 0) {
				// we already grabbed the initial header earlier. This just
				// saves the waste of grabbing it again
				vi[i] = first_i;
				vc[i] = first_c;
				dataoffsets[i] = dataoffset;
			} else {
				// seek to the location of the initial header
				seek_helper(offsets[i]); // !!!
				vi[i] = new Info();
				vc[i] = new Comment();
				if (fetch_headers(vi[i], vc[i], null, null) == -1) {
					dataoffsets[i] = -1;
				} else {
					dataoffsets[i] = offset;
					os.clear();
				}
			}

			// get the serial number and PCM length of this link. To do this,
			// get the last page of the stream
			{
				long end = offsets[i + 1]; // !!!
				seek_helper(end);

				while (true) {
					ret = get_prev_page(og);
					if (ret == -1) {
						// this should not be possible
						vi[i].clear();
						vc[i].clear();
						break;
					}
					if (og.granulepos() != -1) {
						serialnos[i] = og.serialno();
						pcmlengths[i] = og.granulepos();
						break;
					}
				}
			}
		}
	}

	private int make_decode_ready() {
		if (decode_ready)
			System.exit(1);
		vd.synthesis_init(vi[0]);
		vb.init(vd);
		decode_ready = true;
		return (0);
	}

	int open_seekable() throws JOrbisException {
		Info initial_i = new Info();
		Comment initial_c = new Comment();
		int serialno;
		long end;
		int ret;
		int dataoffset;
		Page og = new Page();
		// is this even vorbis...?
		int[] foo = new int[1];
		ret = fetch_headers(initial_i, initial_c, foo, null);
		serialno = foo[0];
		dataoffset = (int) offset; // !!
		os.clear();
		if (ret == -1)
			return (-1);
		if (ret < 0)
			return (ret);
		// we can seek, so set out learning all about this file
		seekable = true;
		fseek(datasource, 0, SEEK_END);
		offset = ftell(datasource);
		end = offset;
		// We get the offset for the last page of the physical bitstream.
		// Most OggVorbis files will contain a single logical bitstream
		end = get_prev_page(og);
		// moer than one logical bitstream?
		if (og.serialno() != serialno) {
			// Chained bitstream. Bisect-search each logical bitstream
			// section. Do so based on serial number only
			if (bisect_forward_serialno(0, 0, end + 1, serialno, 0) < 0) {
				clear();
				return OV_EREAD;
			}
		} else {
			// Only one logical bitstream
			if (bisect_forward_serialno(0, end, end + 1, serialno, 0) < 0) {
				clear();
				return OV_EREAD;
			}
		}
		prefetch_all_headers(initial_i, initial_c, dataoffset);
		return 0;
	}

	int open_nonseekable() {
		// we cannot seek. Set up a 'single' (current) logical bitstream entry
		links = 1;
		vi = new Info[links];
		vi[0] = new Info(); // ??
		vc = new Comment[links];
		vc[0] = new Comment(); // ?? bug?

		// Try to fetch the headers, maintaining all the storage
		int[] foo = new int[1];
		if (fetch_headers(vi[0], vc[0], foo, null) == -1)
			return (-1);
		current_serialno = foo[0];
		make_decode_ready();
		return 0;
	}

	// clear out the current logical bitstream decoder
	void decode_clear() {
		os.clear();
		vd.clear();
		vb.clear();
		decode_ready = false;
		bittrack = 0.f;
		samptrack = 0.f;
	}

	// fetch and process a packet. Handles the case where we're at a
	// bitstream boundary and dumps the decoding machine. If the decoding
	// machine is unloaded, it loads it. It also keeps pcm_offset up to
	// date (seek and read both use this. seek uses a special hack with
	// readp).
	//
	// return: -1) hole in the data (lost packet)
	// 0) need more date (only if readp==0)/eof
	// 1) got a packet

	int process_packet(int readp) {
		Page og = new Page();

		// handle one packet. Try to fetch it from current stream state
		// extract packets from page
		while (true) {
			// process a packet if we can. If the machine isn't loaded,
			// neither is a page
			if (decode_ready) {
				Packet op = new Packet();
				int result = os.packetout(op);
				long granulepos;
				// if(result==-1)return(-1); // hole in the data. For now,
				// swallow
				// and go. We'll need to add a real
				// error code in a bit.
				if (result > 0) {
					// got a packet. process it
					granulepos = op.granulepos;
					if (vb.synthesis(op) == 0) { // lazy check for lazy
						// header handling. The
						// header packets aren't
						// audio, so if/when we
						// submit them,
						// vorbis_synthesis will
						// reject them
						// suck in the synthesis data and track bitrate
						{
							int oldsamples = vd.synthesis_pcmout(null, null);
							vd.synthesis_blockin(vb);
							samptrack += vd.synthesis_pcmout(null, null)
									- oldsamples;
							bittrack += op.bytes * 8;
						}

						// update the pcm offset.
						if (granulepos != -1 && op.e_o_s == 0) {
							int link = (seekable ? current_link : 0);
							int samples;
							// this packet has a pcm_offset on it (the last
							// packet
							// completed on a page carries the offset) After
							// processing
							// (above), we know the pcm position of the *last*
							// sample
							// ready to be returned. Find the offset of the
							// *first*
							//
							// As an aside, this trick is inaccurate if we begin
							// reading anew right at the last page; the
							// end-of-stream
							// granulepos declares the last frame in the stream,
							// and the
							// last packet of the last page may be a partial
							// frame.
							// So, we need a previous granulepos from an
							// in-sequence page
							// to have a reference point. Thus the !op.e_o_s
							// clause above

							samples = vd.synthesis_pcmout(null, null);
							granulepos -= samples;
							for (int i = 0; i < link; i++) {
								granulepos += pcmlengths[i];
							}
							pcm_offset = granulepos;
						}
						return (1);
					}
				}
			}

			if (readp == 0)
				return (0);
			if (get_next_page(og, -1) < 0)
				return (0); // eof. leave unitialized

			// bitrate tracking; add the header's bytes here, the body bytes
			// are done by packet above
			bittrack += og.header_len * 8;

			// has our decoding just traversed a bitstream boundary?
			if (decode_ready) {
				if (current_serialno != og.serialno()) {
					decode_clear();
				}
			}

			// Do we need to load a new machine before submitting the page?
			// This is different in the seekable and non-seekable cases.
			//
			// In the seekable case, we already have all the header
			// information loaded and cached; we just initialize the machine
			// with it and continue on our merry way.
			//
			// In the non-seekable (streaming) case, we'll only be at a
			// boundary if we just left the previous logical bitstream and
			// we're now nominally at the header of the next bitstream

			if (!decode_ready) {
				int i;
				if (seekable) {
					current_serialno = og.serialno();

					// match the serialno to bitstream section. We use this
					// rather than
					// offset positions to avoid problems near logical bitstream
					// boundaries
					for (i = 0; i < links; i++) {
						if (serialnos[i] == current_serialno)
							break;
					}
					if (i == links)
						return (-1); // sign of a bogus stream. error out,
					// leave machine uninitialized
					current_link = i;

					os.init(current_serialno);
					os.reset();

				} else {
					// we're streaming
					// fetch the three header packets, build the info struct
					int foo[] = new int[1];
					int ret = fetch_headers(vi[0], vc[0], foo, og);
					current_serialno = foo[0];
					if (ret != 0)
						return ret;
					current_link++;
					i = 0;
				}
				make_decode_ready();
			}
			os.pagein(og);
		}
	}

	// The helpers are over; it's all toplevel interface from here on out
	// clear out the OggVorbis_File struct
	int clear() {
		vb.clear();
		vd.clear();
		os.clear();

		if (vi != null && links != 0) {
			for (int i = 0; i < links; i++) {
				vi[i].clear();
				vc[i].clear();
			}
			vi = null;
			vc = null;
		}
		if (dataoffsets != null)
			dataoffsets = null;
		if (pcmlengths != null)
			pcmlengths = null;
		if (serialnos != null)
			serialnos = null;
		if (offsets != null)
			offsets = null;
		oy.clear();

		return (0);
	}

	static int fseek(InputStream fis, long off, int whence) {
		if (fis instanceof SeekableInputStream) {
			SeekableInputStream sis = (SeekableInputStream) fis;
			try {
				if (whence == SEEK_SET) {
					sis.seek(off);
				} else if (whence == SEEK_END) {
					sis.seek(sis.getLength() - off);
				} else {
				}
			} catch (Exception e) {
			}
			return 0;
		}
		try {
			if (whence == 0) {
				fis.reset();
			}
			fis.skip(off);
		} catch (Exception e) {
			return -1;
		}
		return 0;
	}

	static long ftell(InputStream fis) {
		try {
			if (fis instanceof SeekableInputStream) {
				SeekableInputStream sis = (SeekableInputStream) fis;
				return (sis.tell());
			}
		} catch (Exception e) {
		}
		return 0;
	}

	// inspects the OggVorbis file and finds/documents all the logical
	// bitstreams contained in it. Tries to be tolerant of logical
	// bitstream sections that are truncated/woogie.
	//
	// return: -1) error
	// 0) OK

	int open(InputStream is, byte[] initial, int ibytes) throws JOrbisException {
		return open_callbacks(is, initial, ibytes);
	}

	int open_callbacks(InputStream is, byte[] initial, int ibytes// , callbacks
																	// callbacks
	) throws JOrbisException {
		int ret;
		datasource = is;

		oy.init();

		// perhaps some data was previously read into a buffer for testing
		// against other stream types. Allow initialization from this
		// previously read data (as we may be reading from a non-seekable
		// stream)
		if (initial != null) {
			int index = oy.buffer(ibytes);
			System.arraycopy(initial, 0, oy.data, index, ibytes);
			oy.wrote(ibytes);
		}
		// can we seek? Stevens suggests the seek test was portable
		if (is instanceof SeekableInputStream) {
			ret = open_seekable();
		} else {
			ret = open_nonseekable();
		}
		if (ret != 0) {
			datasource = null;
			clear();
		}
		return ret;
	}

	// How many logical bitstreams in this physical bitstream?
	public int streams() {
		return links;
	}

	// Is the FILE * associated with vf seekable?
	public boolean seekable() {
		return seekable;
	}

	// returns the bitrate for a given logical bitstream or the entire
	// physical bitstream. If the file is open for random access, it will
	// find the *actual* average bitrate. If the file is streaming, it
	// returns the nominal bitrate (if set) else the average of the
	// upper/lower bounds (if set) else -1 (unset).
	//
	// If you want the actual bitrate field settings, get them from the
	// vorbis_info structs

	public int bitrate(int i) {
		if (i >= links)
			return (-1);
		if (!seekable && i != 0)
			return (bitrate(0));
		if (i < 0) {
			long bits = 0;
			for (int j = 0; j < links; j++) {
				bits += (offsets[j + 1] - dataoffsets[j]) * 8;
			}
			return ((int) Math.rint(bits / time_total(-1)));
		} else {
			if (seekable) {
				// return the actual bitrate
				return ((int) Math.rint((offsets[i + 1] - dataoffsets[i]) * 8
						/ time_total(i)));
			} else {
				// return nominal if set
				if (vi[i].bitrate_nominal > 0) {
					return vi[i].bitrate_nominal;
				} else {
					if (vi[i].bitrate_upper > 0) {
						if (vi[i].bitrate_lower > 0) {
							return (vi[i].bitrate_upper + vi[i].bitrate_lower) / 2;
						} else {
							return vi[i].bitrate_upper;
						}
					}
					return (-1);
				}
			}
		}
	}

	// returns the actual bitrate since last call. returns -1 if no
	// additional data to offer since last call (or at beginning of stream)
	public int bitrate_instant() {
		int _link = (seekable ? current_link : 0);
		if (samptrack == 0)
			return (-1);
		int ret = (int) (bittrack / samptrack * vi[_link].rate + .5);
		bittrack = 0.f;
		samptrack = 0.f;
		return (ret);
	}

	public int serialnumber(int i) {
		if (i >= links)
			return (-1);
		if (!seekable && i >= 0)
			return (serialnumber(-1));
		if (i < 0) {
			return (current_serialno);
		} else {
			return (serialnos[i]);
		}
	}

	// returns: total raw (compressed) length of content if i==-1
	// raw (compressed) length of that logical bitstream for i==0 to n
	// -1 if the stream is not seekable (we can't know the length)

	public long raw_total(int i) {
		if (!seekable || i >= links)
			return (-1);
		if (i < 0) {
			long acc = 0; // bug?
			for (int j = 0; j < links; j++) {
				acc += raw_total(j);
			}
			return (acc);
		} else {
			return (offsets[i + 1] - offsets[i]);
		}
	}

	// returns: total PCM length (samples) of content if i==-1
	// PCM length (samples) of that logical bitstream for i==0 to n
	// -1 if the stream is not seekable (we can't know the length)
	public long pcm_total(int i) {
		if (!seekable || i >= links)
			return (-1);
		if (i < 0) {
			long acc = 0;
			for (int j = 0; j < links; j++) {
				acc += pcm_total(j);
			}
			return (acc);
		} else {
			return (pcmlengths[i]);
		}
	}

	// returns: total seconds of content if i==-1
	// seconds in that logical bitstream for i==0 to n
	// -1 if the stream is not seekable (we can't know the length)
	public float time_total(int i) {
		if (!seekable || i >= links)
			return (-1);
		if (i < 0) {
			float acc = 0;
			for (int j = 0; j < links; j++) {
				acc += time_total(j);
			}
			return (acc);
		} else {
			return ((float) (pcmlengths[i]) / vi[i].rate);
		}
	}

	// seek to an offset relative to the *compressed* data. This also
	// immediately sucks in and decodes pages to update the PCM cursor. It
	// will cross a logical bitstream boundary, but only if it can't get
	// any packets out of the tail of the bitstream we seek to (so no
	// surprises).
	//
	// returns zero on success, nonzero on failure

	public int raw_seek(int pos) {
		if (!seekable)
			return (-1); // don't dump machine if we can't seek
		if (pos < 0 || pos > offsets[links]) {
			// goto seek_error;
			pcm_offset = -1;
			decode_clear();
			return -1;
		}

		// clear out decoding machine state
		pcm_offset = -1;
		decode_clear();

		// seek
		seek_helper(pos);

		// we need to make sure the pcm_offset is set. We use the
		// _fetch_packet helper to process one packet with readp set, then
		// call it until it returns '0' with readp not set (the last packet
		// from a page has the 'granulepos' field set, and that's how the
		// helper updates the offset

		switch (process_packet(1)) {
		case 0:
			// oh, eof. There are no packets remaining. Set the pcm offset to
			// the end of file
			pcm_offset = pcm_total(-1);
			return (0);
		case -1:
			// error! missing data or invalid bitstream structure
			// goto seek_error;
			pcm_offset = -1;
			decode_clear();
			return -1;
		default:
			// all OK
			break;
		}
		while (true) {
			switch (process_packet(0)) {
			case 0:
				// the offset is set. If it's a bogus bitstream with no offset
				// information, it's not but that's not our fault. We still run
				// gracefully, we're just missing the offset
				return (0);
			case -1:
				// error! missing data or invalid bitstream structure
				// goto seek_error;
				pcm_offset = -1;
				decode_clear();
				return -1;
			default:
				// continue processing packets
				break;
			}
		}

		// seek_error:
		// dump the machine so we're in a known state
		// pcm_offset=-1;
		// decode_clear();
		// return -1;
	}

	// seek to a sample offset relative to the decompressed pcm stream
	// returns zero on success, nonzero on failure

	public int pcm_seek(long pos) {
		int link = -1;
		long total = pcm_total(-1);

		if (!seekable)
			return (-1); // don't dump machine if we can't seek
		if (pos < 0 || pos > total) {
			// goto seek_error;
			pcm_offset = -1;
			decode_clear();
			return -1;
		}

		// which bitstream section does this pcm offset occur in?
		for (link = links - 1; link >= 0; link--) {
			total -= pcmlengths[link];
			if (pos >= total)
				break;
		}

		// search within the logical bitstream for the page with the highest
		// pcm_pos preceeding (or equal to) pos. There is a danger here;
		// missing pages or incorrect frame number information in the
		// bitstream could make our task impossible. Account for that (it
		// would be an error condition)
		{
			long target = pos - total;
			long end = offsets[link + 1];
			long begin = offsets[link];
			int best = (int) begin;

			Page og = new Page();
			while (begin < end) {
				long bisect;
				int ret;

				if (end - begin < CHUNKSIZE) {
					bisect = begin;
				} else {
					bisect = (end + begin) / 2;
				}

				seek_helper(bisect);
				ret = get_next_page(og, end - bisect);

				if (ret == -1) {
					end = bisect;
				} else {
					long granulepos = og.granulepos();
					if (granulepos < target) {
						best = ret; // raw offset of packet with granulepos
						begin = offset; // raw offset of next packet
					} else {
						end = bisect;
					}
				}
			}
			// found our page. seek to it (call raw_seek).
			if (raw_seek(best) != 0) {
				// goto seek_error;
				pcm_offset = -1;
				decode_clear();
				return -1;
			}
		}

		// verify result
		if (pcm_offset >= pos) {
			// goto seek_error;
			pcm_offset = -1;
			decode_clear();
			return -1;
		}
		if (pos > pcm_total(-1)) {
			// goto seek_error;
			pcm_offset = -1;
			decode_clear();
			return -1;
		}

		// discard samples until we reach the desired position. Crossing a
		// logical bitstream boundary with abandon is OK.
		while (pcm_offset < pos) {
			int target = (int) (pos - pcm_offset);
			float[][][] _pcm = new float[1][][];
			int[] _index = new int[getInfo(-1).channels];
			int samples = vd.synthesis_pcmout(_pcm, _index);

			if (samples > target)
				samples = target;
			vd.synthesis_read(samples);
			pcm_offset += samples;

			if (samples < target)
				if (process_packet(1) == 0) {
					pcm_offset = pcm_total(-1); // eof
				}
		}
		return 0;

		// seek_error:
		// dump machine so we're in a known state
		// pcm_offset=-1;
		// decode_clear();
		// return -1;
	}

	// seek to a playback time relative to the decompressed pcm stream
	// returns zero on success, nonzero on failure
	int time_seek(float seconds) {
		// translate time to PCM position and call pcm_seek

		int link = -1;
		long pcm_total = pcm_total(-1);
		float time_total = time_total(-1);

		if (!seekable)
			return (-1); // don't dump machine if we can't seek
		if (seconds < 0 || seconds > time_total) {
			// goto seek_error;
			pcm_offset = -1;
			decode_clear();
			return -1;
		}

		// which bitstream section does this time offset occur in?
		for (link = links - 1; link >= 0; link--) {
			pcm_total -= pcmlengths[link];
			time_total -= time_total(link);
			if (seconds >= time_total)
				break;
		}

		// enough information to convert time offset to pcm offset
		{
			long target = (long) (pcm_total + (seconds - time_total)
					* vi[link].rate);
			return (pcm_seek(target));
		}

		// seek_error:
		// dump machine so we're in a known state
		// pcm_offset=-1;
		// decode_clear();
		// return -1;
	}

	// tell the current stream offset cursor. Note that seek followed by
	// tell will likely not give the set offset due to caching
	public long raw_tell() {
		return (offset);
	}

	// return PCM offset (sample) of next PCM sample to be read
	public long pcm_tell() {
		return (pcm_offset);
	}

	// return time offset (seconds) of next PCM sample to be read
	public float time_tell() {
		// translate time to PCM position and call pcm_seek

		int link = -1;
		long pcm_total = 0;
		float time_total = 0.f;

		if (seekable) {
			pcm_total = pcm_total(-1);
			time_total = time_total(-1);

			// which bitstream section does this time offset occur in?
			for (link = links - 1; link >= 0; link--) {
				pcm_total -= pcmlengths[link];
				time_total -= time_total(link);
				if (pcm_offset >= pcm_total)
					break;
			}
		}

		return ((float) time_total + (float) (pcm_offset - pcm_total)
				/ vi[link].rate);
	}

	// link: -1) return the vorbis_info struct for the bitstream section
	// currently being decoded
	// 0-n) to request information for a specific bitstream section
	//
	// In the case of a non-seekable bitstream, any call returns the
	// current bitstream. NULL in the case that the machine is not
	// initialized

	public Info getInfo(int link) {
		if (seekable) {
			if (link < 0) {
				if (decode_ready) {
					return vi[current_link];
				} else {
					return null;
				}
			} else {
				if (link >= links) {
					return null;
				} else {
					return vi[link];
				}
			}
		} else {
			if (decode_ready) {
				return vi[0];
			} else {
				return null;
			}
		}
	}

	public Comment getComment(int link) {
		if (seekable) {
			if (link < 0) {
				if (decode_ready) {
					return vc[current_link];
				} else {
					return null;
				}
			} else {
				if (link >= links) {
					return null;
				} else {
					return vc[link];
				}
			}
		} else {
			if (decode_ready) {
				return vc[0];
			} else {
				return null;
			}
		}
	}

	int host_is_big_endian() {
		return 1;
		// short pattern = 0xbabe;
		// unsigned char *bytewise = (unsigned char *)&pattern;
		// if (bytewise[0] == 0xba) return 1;
		// assert(bytewise[0] == 0xbe);
		// return 0;
	}

	// up to this point, everything could more or less hide the multiple
	// logical bitstream nature of chaining from the toplevel application
	// if the toplevel application didn't particularly care. However, at
	// the point that we actually read audio back, the multiple-section
	// nature must surface: Multiple bitstream sections do not necessarily
	// have to have the same number of channels or sampling rate.
	//
	// read returns the sequential logical bitstream number currently
	// being decoded along with the PCM data in order that the toplevel
	// application can take action on channel/sample rate changes. This
	// number will be incremented even for streamed (non-seekable) streams
	// (for seekable streams, it represents the actual logical bitstream
	// index within the physical bitstream. Note that the accessor
	// functions above are aware of this dichotomy).
	//
	// input values: buffer) a buffer to hold packed PCM data for return
	// length) the byte length requested to be placed into buffer
	// bigendianp) should the data be packed LSB first (0) or
	// MSB first (1)
	// word) word size for output. currently 1 (byte) or
	// 2 (16 bit short)
	//
	// return values: -1) error/hole in data
	// 0) EOF
	// n) number of bytes of PCM actually returned. The
	// below works on a packet-by-packet basis, so the
	// return length is not related to the 'length' passed
	// in, just guaranteed to fit.
	//
	// *section) set to the logical bitstream number

	int read(byte[] buffer, int length, int bigendianp, int word, int sgned,
			int[] bitstream) {
		int host_endian = host_is_big_endian();
		int index = 0;

		while (true) {
			if (decode_ready) {
				float[][] pcm;
				float[][][] _pcm = new float[1][][];
				int[] _index = new int[getInfo(-1).channels];
				int samples = vd.synthesis_pcmout(_pcm, _index);
				pcm = _pcm[0];
				if (samples != 0) {
					// yay! proceed to pack data into the byte buffer
					int channels = getInfo(-1).channels;
					int bytespersample = word * channels;
					if (samples > length / bytespersample)
						samples = length / bytespersample;

					// a tight loop to pack each size
					{
						int val;
						if (word == 1) {
							int off = (sgned != 0 ? 0 : 128);
							for (int j = 0; j < samples; j++) {
								for (int i = 0; i < channels; i++) {
									val = (int) (pcm[i][_index[i] + j] * 128. + 0.5);
									if (val > 127)
										val = 127;
									else if (val < -128)
										val = -128;
									buffer[index++] = (byte) (val + off);
								}
							}
						} else {
							int off = (sgned != 0 ? 0 : 32768);

							if (host_endian == bigendianp) {
								if (sgned != 0) {
									for (int i = 0; i < channels; i++) { // It's
																			// faster
																			// in
																			// this
																			// order
										int src = _index[i];
										int dest = i;
										for (int j = 0; j < samples; j++) {
											val = (int) (pcm[i][src + j] * 32768. + 0.5);
											if (val > 32767)
												val = 32767;
											else if (val < -32768)
												val = -32768;
											buffer[dest] = (byte) (val >>> 8);
											buffer[dest + 1] = (byte) (val);
											dest += channels * 2;
										}
									}
								} else {
									for (int i = 0; i < channels; i++) {
										float[] src = pcm[i];
										int dest = i;
										for (int j = 0; j < samples; j++) {
											val = (int) (src[j] * 32768. + 0.5);
											if (val > 32767)
												val = 32767;
											else if (val < -32768)
												val = -32768;
											buffer[dest] = (byte) ((val + off) >>> 8);
											buffer[dest + 1] = (byte) (val + off);
											dest += channels * 2;
										}
									}
								}
							} else if (bigendianp != 0) {
								for (int j = 0; j < samples; j++) {
									for (int i = 0; i < channels; i++) {
										val = (int) (pcm[i][j] * 32768. + 0.5);
										if (val > 32767)
											val = 32767;
										else if (val < -32768)
											val = -32768;
										val += off;
										buffer[index++] = (byte) (val >>> 8);
										buffer[index++] = (byte) val;
									}
								}
							} else {
								// int val;
								for (int j = 0; j < samples; j++) {
									for (int i = 0; i < channels; i++) {
										val = (int) (pcm[i][j] * 32768. + 0.5);
										if (val > 32767)
											val = 32767;
										else if (val < -32768)
											val = -32768;
										val += off;
										buffer[index++] = (byte) val;
										buffer[index++] = (byte) (val >>> 8);
									}
								}
							}
						}
					}

					vd.synthesis_read(samples);
					pcm_offset += samples;
					if (bitstream != null)
						bitstream[0] = current_link;
					return (samples * bytespersample);
				}
			}

			// suck in another packet
			switch (process_packet(1)) {
			case 0:
				return (0);
			case -1:
				return -1;
			default:
				break;
			}
		}
	}

	public Info[] getInfo() {
		return vi;
	}

	public Comment[] getComment() {
		return vc;
	}

	public void close() throws java.io.IOException {
		datasource.close();
	}

	class SeekableInputStream extends InputStream {
		java.io.RandomAccessFile raf = null;
		final String mode = "r";

		SeekableInputStream(String file) throws java.io.IOException {
			raf = new java.io.RandomAccessFile(file, mode);
		}

		public int read() throws java.io.IOException {
			return raf.read();
		}

		public int read(byte[] buf) throws java.io.IOException {
			return raf.read(buf);
		}

		public int read(byte[] buf, int s, int len) throws java.io.IOException {
			return raf.read(buf, s, len);
		}

		public long skip(long n) throws java.io.IOException {
			return (long) (raf.skipBytes((int) n));
		}

		public long getLength() throws java.io.IOException {
			return raf.length();
		}

		public long tell() throws java.io.IOException {
			return raf.getFilePointer();
		}

		public int available() throws java.io.IOException {
			return (raf.length() == raf.getFilePointer()) ? 0 : 1;
		}

		public void close() throws java.io.IOException {
			raf.close();
		}

		public synchronized void mark(int m) {
		}

		public synchronized void reset() throws java.io.IOException {
		}

		public boolean markSupported() {
			return false;
		}

		public void seek(long pos) throws java.io.IOException {
			raf.seek(pos);
		}
	}

}
